﻿// //   Copyright 2007-2011 Comdiv (F. Sadykov) - http://code.google.com/u/fagim.sadykov/
// //   Supported by Media Technology LTD 
// //    
// //   Licensed under the Apache License, Version 2.0 (the "License");
// //   you may not use this file except in compliance with the License.
// //   You may obtain a copy of the License at
// //    
// //        http://www.apache.org/licenses/LICENSE-2.0
// //    
// //   Unless required by applicable law or agreed to in writing, software
// //   distributed under the License is distributed on an "AS IS" BASIS,
// //   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// //   See the License for the specific language governing permissions and
// //   limitations under the License.
// //   
// //   MODIFICATIONS HAVE BEEN MADE TO THIS FILE

using System;
using System.Collections.Generic;
using System.Linq;
using Comdiv.QWeb.Logging;

namespace Comdiv.QWeb.Events {
	public class QWebEventManager : IQWebEventManager, IRegistryBound {
		private readonly IDictionary<Type, IList<IQWebEventHandler>> cache = new Dictionary<Type, IList<IQWebEventHandler>>();
		private QWebServiceRegistry _registry;

		#region IQWebEventManager Members

		public IQWebEventInvoker<T> OnApplication<T>(QWebContext context, ILogListener log) where T : IQWebEvent, new() {
			lock (this) {
				return new invoker<T>(
					context,
					GetHandlers<T>(),
					_registry.GetApplicationLock(),
					log ?? (null == _registry ? StubLogListener.Default : _registry.Log)
					);
			}
		}

		public IQWebEventInvoker<T> OnThread<T>(QWebContext context, ILogListener log) where T : IQWebEvent, new() {
			lock (this) {
				return new invoker<T>(
					context,
					GetHandlers<T>(),
					null,
					log ?? (null == _registry ? StubLogListener.Default : _registry.Log)
					);
			}
		}

		public void Add<TEvent>(IQWebEventHandler<TEvent> handler, ILogListener log = null)
			where TEvent : IQWebEvent {
			lock (this) {
				log = log ?? (null == _registry ? StubLogListener.Default : _registry.Log);
				if (!cache.ContainsKey(typeof (TEvent))) {
					cache[typeof (TEvent)] = new List<IQWebEventHandler>();
				}
				cache[typeof (TEvent)].Add(handler);
				log.debug("event handler " + handler + " added for " + typeof (TEvent).Name + " event ");
			}
		}

		public void Remove<TEvent>(IQWebEventHandler<TEvent> handler, ILogListener log = null)
			where TEvent : IQWebEvent {
			lock (this) {
				log = log ?? (null == _registry ? StubLogListener.Default : _registry.Log);

				if (!cache.ContainsKey(typeof (TEvent))) {
					return;
				}
				cache[typeof (TEvent)].Remove(handler);
				log.debug("event handler " + handler + " removed from " + typeof (TEvent).Name + " event ");
			}
		}

		public IQWebEventHandler<T>[] GetHandlers<T>() where T : IQWebEvent, new() {
			return cache.ContainsKey(typeof (T))
			       	? cache[typeof (T)].OfType<IQWebEventHandler<T>>().ToArray()
			       	: new IQWebEventHandler<T>[] {};
		}

		#endregion

		#region IRegistryBound Members

		public void SetRegistry(QWebServiceRegistry registry) {
			_registry = registry;
		}

		public QWebServiceRegistry Registry {
			get { return _registry; }
		}

		#endregion

		#region Nested type: invoker

		private class invoker<T> : IQWebEventInvoker<T> where T : IQWebEvent, new() {
			private readonly QWebContext context;
			private readonly IQWebEventHandler[] hs;
			private readonly object locker;
			private readonly ILogListener log;

			public invoker(QWebContext context, IQWebEventHandler[] handlers, object locker, ILogListener log) {
				hs = handlers;
				this.log = log;
				this.locker = locker ?? new object();
				this.context = context;
			}

			#region IQWebEventInvoker<T> Members

			public T Invoke() {
				lock (locker) {
					if (hs.Length == 0) return default(T);
					var e = new T();
					e.SetContext(context);
					foreach (var h in hs) {
						log.debug("start handler " + h, e.Context);
						try {
							h.Process(e);
							log.debug("end handler " + h, e.Context);
						}
						catch (Exception ex) {
							log.error("error handler " + h + " " + ex, e.Context);
						}
					}
					return e;
				}
			}

			#endregion
		}

		#endregion
	}
}